package com.idanchuang.component.base.aspect;


import com.idanchuang.component.base.exception.common.ErrorCode;
import com.idanchuang.component.base.exception.core.ExFactory;
import com.idanchuang.component.core.annotation.WhenPressure;
import com.idanchuang.component.core.helper.EnvContext;
import com.zoi7.component.core.base.BaseClass;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

import java.lang.reflect.Method;

/**
 * Aspect for methods with {@link WhenPressure} annotation.
 *
 * @author yjy
 */
@Aspect
public class WhenPressureAspect extends BaseClass implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    @Pointcut("@annotation(com.idanchuang.component.core.annotation.WhenPressure)")
    public void whenPressureAnnotationPointcut() {
    }

    @Around("whenPressureAnnotationPointcut()")
    public Object invokeWithWhenPressure(ProceedingJoinPoint pjp) throws Throwable {
        Method originMethod = resolveMethod(pjp);
        WhenPressure annotation = originMethod.getAnnotation(WhenPressure.class);
        if (annotation == null) {
            // Should not go through here.
            throw new IllegalStateException("Wrong state for RedisLimit annotation");
        }
        if (!EnvContext.isPressureForAnnotation()) {
            return pjp.proceed();
        }
        if (annotation.returnNull()) {
            return null;
        }
        if (annotation.value() == void.class) {
            throw ExFactory.throwCommon(ErrorCode.NOT_FOUND, "mock bean class unassigned");
        }
        // mock bean class
        Class<?> clazz = annotation.value();
        // mock bean method
        Method pressureMethod = clazz.getDeclaredMethod(originMethod.getName(), originMethod.getParameterTypes());
        Object mockBean = applicationContext.getBean(clazz);
        return pressureMethod.invoke(mockBean, pjp.getArgs());
    }

    private Method resolveMethod(ProceedingJoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Class<?> targetClass = joinPoint.getTarget().getClass();

        Method method = getDeclaredMethodFor(targetClass, signature.getName(),
                signature.getMethod().getParameterTypes());
        if (method == null) {
            throw new IllegalStateException("Cannot resolve target method: " + signature.getMethod().getName());
        }
        return method;
    }

    /**
     * Get declared method with provided name and parameterTypes in given class and its super classes.
     * All parameters should be valid.
     *
     * @param clazz          class where the method is located
     * @param name           method name
     * @param parameterTypes method parameter type list
     * @return resolved method, null if not found
     */
    private Method getDeclaredMethodFor(Class<?> clazz, String name, Class<?>... parameterTypes) {
        try {
            return clazz.getDeclaredMethod(name, parameterTypes);
        } catch (NoSuchMethodException e) {
            Class<?> superClass = clazz.getSuperclass();
            if (superClass != null) {
                return getDeclaredMethodFor(superClass, name, parameterTypes);
            }
        }
        return null;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}
